package com.google.code.swingonfire.component.calendar;

import java.awt.Color;
import java.awt.Font;
import java.util.Calendar;
import java.util.Locale;
import java.util.Map;

import javax.swing.event.EventListenerList;

import com.google.code.swingonfire.utility.Colors;
import com.google.code.swingonfire.utility.Utils;

public abstract class AbstractJCalendarModel implements JCalendarModel
{

    public static final String LOCALE_PROPERTY = "locale";

    private final EventListenerList listeners = new EventListenerList();

    private JCalendarType type = JCalendarType.MONTH;
    private Calendar selected;
    private Calendar displayed;
    private Locale locale;

    public AbstractJCalendarModel(Calendar displayed, Locale locale)
    {
        super();

        setDisplayed(displayed);
        setLocale(locale);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public JCalendarType getType()
    {
        return type;
    }

    @Override
    public void setType(JCalendarType type)
    {
        if (!Utils.equals(this.type, type))
        {
            this.type = type;

            fireCalendarModelTypeChanged();
        }
    }

    @Override
    public void parentType()
    {
        switch (type)
        {
            case ERA:
                throw new UnsupportedOperationException("Not yet implemnted");

            case YEAR:
                setType(JCalendarType.MONTH);
                break;

            case MONTH:
                setType(JCalendarType.YEAR);
                break;

            case WEEK:
                throw new UnsupportedOperationException("Not yet implemnted");
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Calendar getSelected()
    {
        return selected;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setSelected(Calendar selected)
    {
        if (!Utils.equals(this.selected, selected))
        {
            this.selected = selected;

            fireCalendarModelSelectionChanged();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Calendar getDisplayed()
    {
        return displayed;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setDisplayed(Calendar displayed)
    {
        if (!Utils.equals(this.displayed, displayed))
        {
            this.displayed = displayed;

            fireCalendarModelDisplayChanged();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void displayNext()
    {
        Calendar calendar = (Calendar) displayed.clone();

        switch (type)
        {
            case ERA:
                // TODO implement me
                break;

            case YEAR:
                // TODO implement me
                break;

            case MONTH:
                calendar.set(Calendar.DAY_OF_MONTH, 1);
                calendar.add(Calendar.MONTH, 1);
                break;

            case WEEK:
                // TODO implement me
                break;
        }

        setDisplayed(calendar);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void displayPrevious()
    {
        Calendar calendar = (Calendar) displayed.clone();

        switch (type)
        {
            case ERA:
                // TODO implement me
                break;

            case YEAR:
                // TODO implement me
                break;

            case MONTH:
                calendar.set(Calendar.DAY_OF_MONTH, 1);
                calendar.add(Calendar.MONTH, -1);
                break;

            case WEEK:
                // TODO implement me
                break;
        }

        setDisplayed(calendar);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Locale getLocale()
    {
        return locale;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setLocale(Locale locale)
    {
        if (!Utils.equals(this.locale, locale))
        {
            this.locale = locale;

            fireCalendarModelPropertyChanged(LOCALE_PROPERTY);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addCalendarModelListener(JCalendarModelListener listener)
    {
        listeners.add(JCalendarModelListener.class, listener);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void removeCalendarModelListener(JCalendarModelListener listener)
    {
        listeners.remove(JCalendarModelListener.class, listener);
    }

    protected void fireCalendarModelTypeChanged()
    {
        for (JCalendarModelListener listener : listeners.getListeners(JCalendarModelListener.class))
        {
            listener.calendarModelTypeChanged();
        }
    }

    protected void fireCalendarModelSelectionChanged()
    {
        for (JCalendarModelListener listener : listeners.getListeners(JCalendarModelListener.class))
        {
            listener.calendarModelSelectionChanged();
        }
    }

    protected void fireCalendarModelDisplayChanged()
    {
        for (JCalendarModelListener listener : listeners.getListeners(JCalendarModelListener.class))
        {
            listener.calendarModelDisplayChanged();
        }
    }

    protected void fireCalendarModelPropertyChanged(String property)
    {
        for (JCalendarModelListener listener : listeners.getListeners(JCalendarModelListener.class))
        {
            listener.calendarModelPropertyChanged(property);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getTypeBasedName(Calendar calendar)
    {
        switch (getType())
        {
            case ERA:
                throw new IllegalStateException("Unsupported type: " + getType());

            case YEAR:
                return String.valueOf(calendar.get(Calendar.YEAR));

            case MONTH:
                return calendar.getDisplayName(Calendar.MONTH, Calendar.LONG, getLocale()) + ", "
                    + calendar.get(Calendar.YEAR);

            case WEEK:
                return getWeekOfYearName(calendar);
        }

        return null;
    }

    protected int getWeekOfYear(Calendar calendar)
    {
        return calendar.get(Calendar.WEEK_OF_YEAR);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getMonthName(Calendar calendar)
    {
        return calendar.getDisplayName(Calendar.MONTH, Calendar.LONG, getLocale());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Font getMonthFont(Calendar calendar, Font defaultFont)
    {
        return defaultFont.deriveFont(Font.BOLD, defaultFont.getSize() * 1.2f);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Color getMonthForeground(Calendar calendar, Color defaultColor)
    {
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Color getMonthBackground(Calendar calendar)
    {
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getWeekOfYearName(Calendar calendar)
    {
        int weekOfYear = getWeekOfYear(calendar);

        if ("de".equals(getLocale().getLanguage()))
        {
            return weekOfYear + ".";
        }

        switch (weekOfYear % 10)
        {
            case 1:
                return ((weekOfYear % 100) != 11) ? weekOfYear + "st" : weekOfYear + "th";

            case 2:
                return ((weekOfYear % 100) != 12) ? weekOfYear + "nd" : weekOfYear + "th";

            case 3:
                return ((weekOfYear % 100) != 13) ? weekOfYear + "rd" : weekOfYear + "th";

            default:
                return weekOfYear + "th";
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Font getWeekOfYearFont(Calendar calendar, Font defaultFont)
    {
        return defaultFont.deriveFont(Font.PLAIN);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Color getWeekOfYearForeground(Calendar calendar, Color defaultColor)
    {
        return new Color(0x6666ed);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Color getWeekOfYearBackground(Calendar calendar)
    {
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getFirstDayOfWeek()
    {
        return getDisplayed().getFirstDayOfWeek();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getDayOfWeekName(int dayOfWeek)
    {
        Map<String, Integer> names = getDisplayed().getDisplayNames(Calendar.DAY_OF_WEEK, Calendar.SHORT, getLocale());

        for (Map.Entry<String, Integer> entry : names.entrySet())
        {
            if (dayOfWeek == entry.getValue())
            {
                return entry.getKey();
            }
        }

        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Font getDayOfWeekFont(int dayOfWeek, Font defaultFont)
    {
        return defaultFont.deriveFont(Font.PLAIN);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Color getDayOfWeekForeground(int dayOfWeek, Color defaultColor)
    {
        return new Color(0x6666ed);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Color getDayOfWeekBackground(int dayOfWeek)
    {
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getDayName(Calendar calendar, boolean inScope)
    {
        return String.valueOf(calendar.get(Calendar.DAY_OF_MONTH));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Font getDayFont(Calendar calendar, boolean inScope, Font defaultFont)
    {
        return defaultFont.deriveFont(Font.BOLD, defaultFont.getSize() * 1.2f);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Color getDayForeground(Calendar calendar, boolean inScope, Color defaultColor)
    {
        Color result = defaultColor;
        int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);

        if (dayOfWeek == Calendar.SUNDAY)
        {
            result = new Color(0xaa0000);
        }

        return (inScope) ? result : Colors.translucent(result, 0.25);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Color getDayBackground(Calendar calendar, boolean inScope)
    {
        return null;
    }

}
